ext.define('extension.filters', function() {

var utils = extension.utils;
var messages = extension.messages;
var data = Object.create(null);
var cache = Object.create(null);
var defaults = Object.freeze({
    'data.patternlistA': [],
    'data.patternlistC': [],
    'data.patternlistB': [],
    'data.patternlistD': [],
    'data.patternlistE': [],
    'data.patternlistF': []
});

function updateTarget(k, u, p, b) {
    var f = utils.copy(getFilter(k)), r = utils.regexp;
    for (var i = 0; i < f.length; i++) {
        if (r(f[i].pattern).test(u)) {
            f[i].enabled = +b;
            setFilter(k, f);
            return;
        }
    }
    f.push({enabled: +b, pattern: p});
    setFilter(k, f);
}

function extractTargets(a) {
    var n = [], r = utils.regexp;
    for (var i = 0; i < a.length; i++)
        a[i].enabled && n.push(r(a[i].pattern));
    return n;
}

function extractSources(a) {
    var n = [];
    for (var i = 0; i < a.length; i++) {
        if (!a[i].root || !a[i].enabled)
            continue;
        var p = [], q = a[i].patterns;
        for (var j = 0; j < q.length; j++)
            q[j].enabled && p.push(q[j].pattern);
        if (p.length)
            n.push({root: a[i].root, patterns: p});
    }
    return n;
}

function searchObject(k, u) {
    var n = [], r = utils.regexp;
    if (k in cache) {
        var f = cache[k];
        for (var i = 0; i < f.length; i++) {
            if (r(f[i].root).test(u))
                n = n.concat(f[i].patterns);
        }
    }
    return n;
}

function searchArray(k, u) {
    if (k in cache) {
        for (var i = 0, c = cache[k]; i < c.length; i++) {
            if (c[i].test(u))
                return c[i];
        }
    }
}

function searchAny(k) {
    return k in cache ? cache[k] : {};
}

function getFilter(o) {
    if (typeof o === 'string')
        return data[o] != null ? data[o] : defaults[o];
    return utils.scan(data, defaults);
}

function getChanges() {
    return utils.merge({}, data);
}

function setFilter(o) {
    var p = utils.object(arguments[0], arguments[1]);
    var changes = {}, rx = /(patternlist[BDEF])$/;
    for (var k in p) {
        if (k in defaults) {
            var c = {};
            if (k in data) {
                if (utils.equals(p[k], data[k]))
                    continue;
                c.oldValue = data[k];
            }
            if (p[k] == null || utils.equals(p[k], defaults[k])) {
                if (data[k] != null)
                    c.oldValue = data[k];
                cache[k] = defaults[k];
                delete data[k];
            }
            else {
                cache[k] = rx.test(k) ? extractSources(p[k]) : extractTargets(p[k]);
                c.newValue = data[k] = p[k];
            }
            if ('oldValue' in c || 'newValue' in c)
                changes[k] = c;
        }
    }
    if (Object.keys(changes).length) {
        changes = utils.diff(changes, getFilter());
        messages.send('change-settings', changes);
    }
}

function updateWhitelist(u, p, b) {
    updateTarget('data.patternlistA', u, p, b);
}

function updateBlacklist(u, p, b) {
    updateTarget('data.patternlistC', u, p, b);
}

function matchWhitelist(u) {
    return searchArray('data.patternlistA', u);
}

function matchBlacklist(u) {
    return searchArray('data.patternlistC', u);
}

function matchSelective(u) {
    return [
        searchObject('data.patternlistB', u),
        searchObject('data.patternlistD', u),
        searchAny('data.patternlistE'),
        searchAny('data.patternlistF')
    ];
}

return {
    bind: function() {
        messages.listen({
            'account-data': setFilter
        });
    },
    get: getFilter,
    getChanges: getChanges,
    set: setFilter,
    updateWhitelist: updateWhitelist,
    updateBlacklist: updateBlacklist,
    matchWhitelist: matchWhitelist,
    matchBlacklist: matchBlacklist,
    matchSelective: matchSelective
};

});
